8487 2020-04-05 2024-11-10
本篇将着重分析Spring是如何处理一个请求的。
一、DispatcherServlet
在Spring完全启动后,服务器就可以正常响应web请求了。我们看下请求入口。
1、FrameworkServlet
// org.springframework.web.servlet.FrameworkServlet
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
// 省略其他
}
// 本质是一个 Servlet
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
// 省略其他
}
// 三个方法均来自 DispatcherServlet 的父类 FrameworkServlet
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 记录请求处理耗时
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 一般为null
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 整合request中的Locale
LocaleContext localeContext = buildLocaleContext(request);
// 默认为null
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 简单的将request和response放进类 ServletRequestAttributes中
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 真正的逻辑处理doService留给了子类DispatcherServlet
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
方法中已经开始了对请求的处理,虽然把细节转移到了doService方法中实现,但是我们不难看出来处理请求前后所做的不少准备与结尾工作,如下
- 为了保证当前线程的LocaleContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性。
- 根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。
- 委托给doService方法进一步处理。
- 请求处理结束后恢复线程到原始状态。
- 请求处理结束后发布事件通知。
1、对Locale的处理
// 子类DispatchServlet重写了父类FrameServlet的buildLocaleContext方法
// 注意,虽然上面3个方法是调用父类的,但实际请求发送给的是DispatchServlet,而并非FrameServlet
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
// 这就是上节我们提到过的 LocaleResolver,默认为AcceptHeaderLocaleResolver
LocaleResolver lr = this.localeResolver;
if (lr instanceof LocaleContextResolver) {
return ((LocaleContextResolver) lr).resolveLocaleContext(request);
}
// 走到这里
else {
return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
}
}
// 进到这里来了
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 默认为null,可以自定义
Locale defaultLocale = getDefaultLocale();
// 如果默认Locale不为null且浏览器端没有指定语言环境
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
// 进一步进tomcat走了一万个方法
// 最后得出requestLocale = zh_CN
Locale requestLocale = request.getLocale();
// 默认为空
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
LocaleResolver默认实现为
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
2、WebAsyncManager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 简单的注册属性,暂时还没发现有什么用途
public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {
WebAsyncManager asyncManager = null;
Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);
if (asyncManagerAttr instanceof WebAsyncManager) {
asyncManager = (WebAsyncManager) asyncManagerAttr;
}
if (asyncManager == null) {
asyncManager = new WebAsyncManager();
servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);
}
return asyncManager;
}
// 暂时还没发现有什么用
/**
* CallableProcessingInterceptor implementation that initializes and resets
* FrameworkServlet's context holders, i.e. LocaleContextHolder and RequestContextHolder.
*/
private class RequestBindingInterceptor implements CallableProcessingInterceptor {
@Override
public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
// 这一行
initContextHolders(request, buildLocaleContext(request),
buildRequestAttributes(request, response, null));
}
}
@Override
public <T> void postProcess(NativeWebRequest webRequest, Callable<T> task, Object concurrentResult) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
resetContextHolders(request, null, null);
}
}
}
3、initContextHolders*
// 为RequestContextHolder注入HttpServletRequest,绑定到ThreadLocal
private void initContextHolders(HttpServletRequest request,
@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
}
这个地方初看可能会觉得很奇怪,可仔细一跟源码就会豁然开朗,如下
// RequestContextHolder逻辑一模一样,这里就不列了
public final class LocaleContextHolder {
private static final ThreadLocal<LocaleContext> localeContextHolder =
new NamedThreadLocal<>("LocaleContext");
private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
new NamedInheritableThreadLocal<>("LocaleContext");
// 省略其他
}
public static void setLocaleContext(@Nullable LocaleContext localeContext, boolean inheritable) {
if (localeContext == null) {
resetLocaleContext();
}
else {
if (inheritable) {
inheritableLocaleContextHolder.set(localeContext);
localeContextHolder.remove();
}
else {
localeContextHolder.set(localeContext);
inheritableLocaleContextHolder.remove();
}
}
}
于是我们可以在单个请求中的任何地方调用如下代码以后去request和response,形如
@RequestMapping("/testStr")
@ResponseBody
public String testStr() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
return request + " " + response;
}
4、MVC默认组件
来自jar包 org.springframework:spring-webmvc:5.2.5.REALEASE。
除此之外,Spring Boot为了简化开发&用户体验,会替我们自动注入绝大部分组件,以满足基本可用。
注意,即使 Spring Boot 不显式注入组件,Spring MVC 自带的默认组件其实满足基本需求的。但为了灵活性和便于管理,Spring Boot替我们做了绝大部分配置工作(显式注入配置 > 默认配置,所以其实默认组件配置是没有生效的)。
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
二、doService
接下来重点看下doService方法,如下
// 来自 FrameworkServlet 子类 DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// debug一下请求
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
// 这一段是对jsp中include指令的支持,具体细节这里暂时跳过去
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
// set进了DispatchServlet的WebApplicationContext
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// 对FlashMap的处理,解决redirect中传递参数失效的问题
// forward不存在参数丢失的问题
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// 这里才是真正的逻辑调用
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doService方法中并没有过多的逻辑,除了注入request一些框架组件外(方便后面的处理),还有就是对 FlashMap 的处理了。如下是一个简单示例
// 目前只发现这一种实现方式,经过多番实践,这个东西并没有什么卵用,建议忘掉 FlashMap
@RequestMapping("/redirect2")
public String redirect2(String username, String password, RedirectAttributes redirectAttributes) {
// 这里传入的参数会出现在重定向后的url
redirectAttributes.addAttribute("username", username);
redirectAttributes.addAttribute("password", password);
return "redirect: /redirectTarget";
}
@RequestMapping("/redirectTarget")
@ResponseBody
public String redirectTarget(String username, String password, HttpServletRequest request) {
return "username=" + username + ",password=" + password;
}
1、doDispatch
doService将真正逻辑处理委托给了doDispatch方法,如下
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 如果是MultiparContent类型的request类型则转换request为MultipartHttpServletRequest类型的request
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 根据request信息寻找对应的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果没有找到对应的handler则通过response反馈错误信息
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 根据当前handler寻找对应的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 拦截器的preHandler方法的调用
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 真正的激活handler并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 视图名称转换应用于需要添加前缀后缀的情况
applyDefaultViewName(processedRequest, mv);
// 应用所有拦截器的postHander方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理请求返回的处理结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 触发 AfterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 触发 AfterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
doDispatch方法中展示了Spring请求处理所涉及的主要逻辑,而我们之前设置在request中的各种辅助属性也都有被派上了用场。下面回顾以下逻辑处理的全过程。
2、checkMultipart
对于请求的处理,Spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request。
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 如果指定了MultipartResolver
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
}
else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
// 来源于类 StandardServletMultipartResolver
// 一般是根据multipart/form-data
@Override
public boolean isMultipart(HttpServletRequest request) {
// Same check as in Commons FileUpload...
if (!"post".equalsIgnoreCase(request.getMethod())) {
return false;
}
String contentType = request.getContentType();
return StringUtils.startsWithIgnoreCase(contentType, "multipart/");
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
// 后面就是对http协议中的文件转换了
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {
super(request);
if (!lazyParsing) {
parseRequest(request);
}
}
3、getHandler
// Determine handler for the current request.
// 根据request信息寻找对应的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果没有找到对应的handler则通过response反馈错误信息
noHandlerFound(processedRequest, response);
return;
}
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 注意这里的handlerMappings的个数为5个
// 原因是加载配置文件时,自定义标签mvc已经配置好了需要的bean
// 为 RequestMappingHandlerMapping、BeanNameUrlHandlerMapping
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// 遍历5个hander,找到则返回,找不到则404
HandlerExecutionChain handler = mapping.getHandler(request);
// 如果找对对应的handler则直接返回,默认优先查 RequestMappingHandlerMapping,第一优先级
if (handler != null) {
// 找到即返回
return handler;
}
}
}
return null;
}
在之前的内容我们提过,在系统启动时Spring会将所有的映射类型的bean注册到this.handlerMappings变量中,所以此方法的目的就是遍历所有的HanderMapping,并调用getHandler方法进行封装处理。其中,5个 HandlerMapping 分别为
- RequestMappingHandlerMapping:支持 @RequestMapping、@GetMapping 等注解,最常用。
- **BeanNameUrlHandlerMapping:**根据beanName寻找路由,可忽略。
- **RouterFunctionMapping:**支持函数式路由编程 RouterFunction,这个在Web Flux里面用的比较多。
- SimpleUrlHandlerMapping:简单url路径匹配bean,可忽略。
- WelcomePageHandlerMapping:匹配默认首页 /。
以上配置来自Spring Boot默认配置类 WebMvcAutoConfiguration,后两个组件为Spring Boot独有。
1、RequestMapping
以 RequestMappingHandlerMapping 为例查看其 getHandler 方法。
// getHandler方法还是由父类 AbstractHandlerMapping 提供
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 抽象方法,由子类重写
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 执行拦截器链
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
// 跨域问题
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
// 类RequestMappingHandlerMapping重写了父类方法
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
try {
// 又调回了父类
return super.getHandlerInternal(request);
}
finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}
2、getHandlerInternal
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 截取用于匹配的url有效路径,如请求是${app}/test_url/a,那么这里就是test_url/a
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
// 获取内部维护的读写锁ReentrantReadWriteLock的读锁
// 内部缓存了Controller处理方法与url对映射关系
this.mappingRegistry.acquireReadLock();
try {
// 根据路径寻找Handler
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
// 释放读锁
this.mappingRegistry.releaseReadLock();
}
}
// 来自类 AbstractHandlerMethodMapping,找到匹配的 HandlerMethod
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 先查找缓存,自定义标签mvc会事先解析所有请求并存进这里,所以这里一般不为空
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
3、添加拦截器
在寻找到 HandlerMethod 后,接着将配置中的对应的拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
// HandlerInterceptor Spring拦截器
// 这里默认有两个 ConversionServiceExposingInterceptor、ResourceUrlProviderExposingInterceptor
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
// 走到这里,记录拦截链
chain.addInterceptor(interceptor);
}
}
return chain;
}
4、处理跨域
// 可以看到是在这一行就进行CORS判断,两个条件:
// 1. 是否配置了CORS,如果不配的话,默认是返回false的
// 2. 或者当前请求是OPTIONS请求,且头里包含ORIGIN和ACCESS_CONTROL_REQUEST_METHOD
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
// 添加 CorsInterceptor 拦截器
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
@Nullable
private final CorsConfiguration config;
public CorsInterceptor(@Nullable CorsConfiguration config) {
this.config = config;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// Consistent with CorsFilter, ignore ASYNC dispatches
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
if (asyncManager.hasConcurrentResult()) {
return true;
}
return corsProcessor.processRequest(this.config, request, response);
}
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.config;
}
}
4、noHandlerFound
每个请求都应该对应着一个Hander,因为每个请求都会在后台有相应的逻辑对应,而逻辑的实现就是在Handler中,所以一旦遇到没有找到相应Handler的情况(包含默认),就只能通过response向用户返回错误信息。
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
}
// 默认为false
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
5、getHandlerAdapter
我们先理一下概念:
-
HandlerMapping:定于url与HandlerMethod的匹配关系,如 RequestMappingHandlerMapping。
-
HandlerMethod:url所对应的具体bean处理方法。
-
HandlerExecutionChain:Spring拦截器链,用于执行拦截器。
-
HandlerAdapter:用于执行HandlerMethod。
这里我们重点看一下 RequestMappingHandlerAdapter。
注意,这里只是找到,还未进行调用。
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 这一步只是寻找,判断的规则也很简单,如下
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
// 这里默认会有四个,分别是 RequestMappingHandlerAdapter、HandlerFunctionAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter,优先级依次降低
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
// 来源于类 AbstractHandlerMethodAdapter,一般用于处理@RequestMapping注解,与@Controller配合使用
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
// 来自于类 RequestMappingHandlerAdapter
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
更为复杂的调用逻辑是在后面实现的(如拦截器链条逻辑的应用)。
6、LastModified
在研究Spring对缓存处理的功能支持前,我们先了解一个概念:Last-Modified缓存机制。
- 在客户端第一次输入URL时,服务端会返回内容和状态吗200,表示请求成功,同时会添加一个“Last-Modified”的响应头,表示此文件在服务器上的最后更新时间,例如“Last-Modified:Wed, 14 Mar 2020 20:26:22 GMT”。
- 客户端第二次请求此URL时,客户端会向服务器发送请求头"If-Modified-Since",询问服务器该时间之后当前请求内容是否有被修改过,如果服务器端的内容没有变化,则自动返回HTTP 304状态码(只要响应头,内容为空,这样就节省了网络带宽)。
Spring提供了对 Last-Modified 机制的支持,只需要实现LastModified接口,如下所示
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
@Component("/last")
public class LastModifiedCacheController extends AbstractController implements LastModified {
private long lastModifiedTime;
private static final DateTimeFormatter DTF_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
String str = parseLongTime(lastModifiedTime);
response.setCharacterEncoding("utf-8");
response.getWriter().write("<h2>当前时间:" + str + "<h2>");
return null;
}
@Override
public long getLastModified(HttpServletRequest request) {
if (lastModifiedTime == 0) {
lastModifiedTime = System.currentTimeMillis();
}
// 这一行决定了是否总是返回最新值,注释掉则每次都返回第一次访问的时间,不注释则每次返回最新时间
// lastModifiedTime = System.currentTimeMillis();
return lastModifiedTime;
}
public static String parseLongTime(long longTime) {
Instant instant = Instant.ofEpochMilli(longTime);
LocalDateTime nowTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
return DTF_DATE_TIME.format(nowTime);
}
}
Spring判断是否过期,通过判断请求的“If-Modified-Since”是否大于等于当前的getLastModified方法的时间戳,如果是,则认为没有修改。否则认为修改了返回最新值。
三、处理请求
上面的都是一些准备工作,这一块才算是正式执行自定义业务相关代码了。
1、applyPreHandle
Servlet API定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对它进行前置处理和后置处理。
此外有些时候,你可能只想处理由某些Spring MVC组件处理的Web请求,并在这些处理程序返回的模型属性被传递到视图之前,对他们进行一些操作。
这时就到了HandlerInterceptor ,如下例子。
// 自定义拦截器
public class TestInterceptor implements HandlerInterceptor {
private Logger log = LogManager.getLogger(this.getClass());
/**
* 如果请求放行(调用Controller之前),则返回true,否则返回false
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("放行请求:" + request.getRequestURI());
sleep(100);
return true;
}
/**
* 请求已被处理完(调用Controller之后,返回逻辑视图之前),调用此方法
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.debug("执行请求完,返回" + response.getStatus());
sleep(100);
}
/**
* 在返回逻辑试图之后(通常用来释放资源),调用此方法
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.debug("关闭连接资源");
sleep(100);
}
}
1、applyPreHandle
// 通过前文我们已经看到了 spring web添加了两个默认的拦截器
// ConversionServiceExposingInterceptor,数据转换与绑定
// ResourceUrlProviderExposingInterceptor,支持访问静态资源
// 另外,如果配置允许跨域,则会多一个拦截器 CorsInterceptor
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// Spring会帮我们内置两个HandlerInterceptor,用于实现Web的特定拦截,只有返回true,则会放行请求
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
// 即使不通过,仍会执行 triggerAfterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
2、applyPostHandle
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 还是前面两个,需要主要的是,后处理的执行顺序是倒过来的,这个与Spring Cloud Gateway很像
// 即前面是123经过拦截器,后面是321流出拦截器
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
注意这里请求流入与流出的顺序。
2、ha.handle**
handler方法算是Spring MVC中的重中之重,主要分为两个部分,在找到目标方法后,如何调用方法?又怎么处理方法的返回值?这里为了具体化,我简单列几个比较关注的点吧,如下
- 调用方法中参数是如何确定、如何精确绑定的?包括个数、类型
- @RequestParam、@ResponseBody、@PathVariable等注解如何生效?
- 如何处理返回的非String类型对象?
- Param参数自动注入的是如何进行的?
1、ModelAndView
先看下Spring MVC中MV的定义。这里的M - model可能有点狭义,但V - view还是比较好理解的。
// Actually invoke the handler.
// 如果是视图页面,则mv不为null,反之则为null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
public class ModelAndView {
/** View instance or view name String. */
@Nullable
private Object view;
/** Model Map. */
@Nullable
private ModelMap model;
/** Optional HTTP status for the response. */
@Nullable
private HttpStatus status;
/** Indicates whether or not this instance has been cleared with a call to {@link #clear()}. */
private boolean cleared = false;
// 省略其他get/set方法
}
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
// 确定返回类型,json or html/text or 二进制流
@Nullable
default String getContentType() {
return null;
}
// 渲染视图界面
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
2、handleInternal
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
// 这里还是以ReqeustMappingHandlerAdapter为例
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
// 我们主要看下这里,这一步之后已经确定入参参数以及需要返回值类型
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 这里的参数解析支持有26种,后文简单列下
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// 返回值解析有15种
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 这个mvcContainer很重要
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 这里主要是一些对于异步的支持
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 实际调用处,我们主要看下这个方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 这里已经取得了返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 这一步主要是解析返回值类型
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 这一步过后,Spring MVC已经可以得到了特定位置参数的值,即已经完成了参数的自动注入
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 接下来开始利用参数调用方法,这里返回的是方法结果值
return doInvoke(args);
}
实际的Controller方法调用并不复杂,找到对应的Bean调用即可。
关键是对于此方法的入参(例如支持Request、Map、String、表单/json、自定义类型注入等),以及返回值的处理(可以返回String、ModelAndView、还有二进制流等)。
3、入参和返回值
1、26种HandlerMethodArgumentResolver
这里列下之前提到的26种用于解析参数的类,如下(限于篇幅不做过多展开,大致作用就是给与特定参数以特定资源)。
当然,我们也完全可以自定义HandlerMethodArgumentResolver,已实现自定类型的资源注入。
0 = {RequestParamMethodArgumentResolver@6892}
1 = {RequestParamMapMethodArgumentResolver@6893}
2 = {PathVariableMethodArgumentResolver@6894}
3 = {PathVariableMapMethodArgumentResolver@6895}
4 = {MatrixVariableMethodArgumentResolver@6896}
5 = {MatrixVariableMapMethodArgumentResolver@6897}
6 = {ServletModelAttributeMethodProcessor@6898}
7 = {RequestResponseBodyMethodProcessor@6899}
8 = {RequestPartMethodArgumentResolver@6900}
9 = {RequestHeaderMethodArgumentResolver@6901}
10 = {RequestHeaderMapMethodArgumentResolver@6902}
11 = {ServletCookieValueMethodArgumentResolver@6903}
12 = {ExpressionValueMethodArgumentResolver@6904}
13 = {SessionAttributeMethodArgumentResolver@6905}
14 = {RequestAttributeMethodArgumentResolver@6906}
15 = {ServletRequestMethodArgumentResolver@6907}
16 = {ServletResponseMethodArgumentResolver@6908}
17 = {HttpEntityMethodProcessor@6909}
18 = {RedirectAttributesMethodArgumentResolver@6910}
19 = {ModelMethodProcessor@6911}
20 = {MapMethodProcessor@6912}
21 = {ErrorsMethodArgumentResolver@6913}
22 = {SessionStatusMethodArgumentResolver@6914}
23 = {UriComponentsBuilderMethodArgumentResolver@6915}
24 = {RequestParamMethodArgumentResolver@6916}
25 = {ServletModelAttributeMethodProcessor@6917}
大致过一下这些类的定义,会发现Spring默认提供了很多功能,但我们平时常用的就那么一点,这一部分有待研究,暂时跳过。
如下是一个实际的例子
public class RequestContextArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
// 需要注入资源的类
return methodParameter.getParameterType().equals(RequestContext.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
// 事先在拦截器进行token验证以及token存放
RequestContext requestContext = (RequestContext) request.getAttribute("XX_TOKEN");
// 如果为空则表示没有传token或者token已过期
if (requestContext == null || requestContext.getUserInfo() == null) {
throw new LoginExpireException();
}
return requestContext;
}
}
2、15种HandlerMethodReturnValueHandler
这里列下之前提到的15种用于解析方法返回值的类,如下限于篇幅不做过多展开,大致作用就是根据方法返回的不同类型,决定请求的响应结果)。
0 = {ModelAndViewMethodReturnValueHandler@6501}
1 = {ModelMethodProcessor@6502}
2 = {ViewMethodReturnValueHandler@6503}
3 = {ResponseBodyEmitterReturnValueHandler@6504}
4 = {StreamingResponseBodyReturnValueHandler@6505}
5 = {HttpEntityMethodProcessor@6506}
6 = {HttpHeadersReturnValueHandler@6507}
7 = {CallableMethodReturnValueHandler@6508}
8 = {DeferredResultMethodReturnValueHandler@6509}
9 = {AsyncTaskMethodReturnValueHandler@6510}
10 = {ModelAttributeMethodProcessor@6511}
11 = {RequestResponseBodyMethodProcessor@6512}
12 = {ViewNameMethodReturnValueHandler@6513}
13 = {MapMethodProcessor@6514}
14 = {ModelAttributeMethodProcessor@6515}
大概还是跟上面一样吧,Spring默认提供了很多功能,兼顾了绝大部分场景,这里不做过多展开。
3、自定义参数和返回值
1、顶层接口
通过代码,我们知道了上述过程,虽然不知道内部的实现细节。我们照葫芦画瓢,试着去自己实现一个,先上父接口
// 最顶层解析参数的接口
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
// 顶层接口处理返回值
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
// 顶层接口转换结果消息,一般用于 @ResponseBody 的转换
// 而对于@ResponseBody处理的类为 RequestResponseBodyMethodProcessor
// 该类同时实现了 HandlerMethodArgumentResolver 和 HandlerMethodReturnValueHandler 接口
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
2、关键代码
关键代码如下
@RequestMapping("/testParam")
// 如果使用@ResponseBody的话,等于是选择了RequestResponseBodyMethodProcessor来处理返回值
// 这是可能需要自定义一个HttpMessageConverter,以便让RequestResponseBodyMethodProcessor进行对象转换
public User testParam(@CurrentUser User user) {
System.out.println(user);
return user;
}
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 存在一个为 User 类型且被 @CurrentUser 注解修饰的参数
return parameter.getParameterType().isAssignableFrom(User.class)
&& parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
// 给予资源
User user = new User();
user.setId(1);
user.setName("11");
user.setAge(21);
return user;
}
}
public class UserMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
System.out.println("supportsReturnType" + " " + returnType + " " + returnType.getParameterType());
// 支持返回类型为 User.class
return returnType.getParameterType() == User.class;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 不渲染视图,自定义处理
mavContainer.setRequestHandled(true);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
System.out.println("handleReturnValue" + returnValue + " " + returnType + " " + mavContainer + " " + webRequest);
response.getWriter().write(returnValue.toString());
response.flushBuffer();
}
}
通过实现 WebMvcConfigurer 类的 addArgumentResolvers、addReturnValueHandlers 方法以在Spring Boot快速注入自定义组件。
3、自定义HttpMessageConverter
需要配合 HttpMessageConverter,不然Spring Boot无法正确识别Http状态码。所以,非必要情况下,我们不需要自定返回参数解析器。
@RequestMapping("/testParam")
@ResponseBody
public User testParam(@CurrentUser User user) {
System.out.println(user);
return user;
}
@Component
public class UserMessageConvert implements HttpMessageConverter<User>, ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
RequestMappingHandlerAdapter adapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
if (!adapter.getMessageConverters().contains(this)) {
adapter.getMessageConverters().add(this);
System.out.println("onApplicationEvent成功添加自定义转换器");
}
}
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return clazz == User.class;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz == User.class;
}
@Override
public List<MediaType> getSupportedMediaTypes() {
// 不能返回null
List<MediaType> list = new ArrayList<MediaType>(2);
list.add(MediaType.TEXT_PLAIN);
return list;
}
@Override
public User read(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputMessage.getBody()));
StringBuilder s = new StringBuilder();
String temp;
while ((temp = bufferedReader.readLine()) != null) {
s.append(temp);
}
System.out.println(s);
return null;
}
@Override
public void write(User user, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
outputMessage.getBody().write(user.toString().getBytes());
}
}
限于篇幅,点到为止。
4、applyDefaultViewName
// 视图名称转换应用于需要添加前缀后缀的情况
applyDefaultViewName(processedRequest, mv);
/**
* Do we need view name translation?
*/
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
// 如果返回的是视图页面,但有没有指定视图名称,则返回默认视图
if (mv != null && !mv.hasView()) {
// 添加前后缀,由 RequestToViewNameTranslator viewNameTranslator 提供支持
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
mv.setViewName(defaultViewName);
}
}
}
四、收尾工作
Spring在这一步对请求结果进行处理,不管最终的结果是流数据、还是视图界面、还是抛出异常,都在这里统一处理。我们先来回顾一下代码,如下
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// 省略部分代码
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
// 这里记录一下可能出现的异常
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
// 而针对Error,则直接抛错即可
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 这里
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 这里调用拦截器中afterCompletion
// 注意这里把异常传进去了
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
// 如果处理请求过程中出现了异常
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
// 大部分情况下异常捕获会走到此处
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 针对异常做全局处理
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 如果返回的是一个页面,那么这一步开始渲染界面
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
1、异常处理
对于找不到的请求url,可以直接报404,但对于正常情况下的服务器处理请求错误,那么就会进到这里,可以决定是否报500还是200。
// 方法均来自类 DispatchServlet
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
// Spring Boot帮我们内置了2个 DefaultErrorAttributes、HandlerExceptionResolverComposite
// 其中第二个位复合型,又包含3个子项:ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
// 有一个能够找到,则说明返回结果
if (exMv != null) {
break;
}
}
}
// 如果exMV为null,则说明异常处理器不能处理该异常,不为null则代表以处理
if (exMv != null) {
// 处理为空的特例,只返回状态码
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
// 无法处理,则直接抛出去
throw ex;
}
2、渲染界面
Spring Boot中常见的html、jsp、thymeleaf、beetl界面渲染就是此步进行的。我们看下渲染界面的逻辑,如下
// 方法均来自类 DispatchServlet
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
// 解析loccal,国内一般为zh_CN,可参考前面对Locale的处理
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 开始根据视图名解析视图了
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 找不到视图对应的页面
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 开始渲染,输出字节流
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
// 注意这里的逻辑,如果有多个视图解析器的话,那么返回第一个视图解析器能够处理的View
// 在有多个视图解析器的情况下,这一步可能会发送意料之外的情况,例如期待a页面被解析器1解析,而却被解析器2解析
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
// 这一步留给了视图解析器很大的空间,我就在这一步中遇到过坑,这一步里面包含的view资源可能存在,也可能不存在,看解析器里面是否做了资源存在性的检查
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
// 这里以类 InternalResourceViewResolver 为例,大致过下
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 是否启用缓存,默认为true
if (!isCache()) {
return createView(viewName, locale);
}
else {
// 所以一般走的是缓存,如果有的话
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
// 第一次创建页面
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
// 可能有资源存在性的检查,这里示例代码是没有的
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
// 这里使用Servlet中标准的RequestDispatcher来进行转发
// Spring并不直接负责文件页面的输出,而是委托给了Servlet容器
rd.forward(request, response);
}
}
3、triggerAfterCompletion
// 调用拦截器中的各个afterCompletion
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, ex);
}
throw ex;
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
// 倒序调用,且传入异常信息
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
至此,Spring MVC总算告一段落了。后续将不定时深入分析Spring在工作常见的一些问题及其背后源码、设计思想,希望能对读者有所帮助。
总访问次数: 23次, 一般般帅 创建于 2020-04-05, 最后更新于 2024-11-10
欢迎关注微信公众号,第一时间掌握最新动态!